/**
 * @file spi_switch.c
 * @author windowsair
 * @brief SPI模式和IO模式之间的切换
 * @change: 2020-11-25 第一版
 *          2021-2-11  传输模式切换测试通过
 *          2022-9-15  支持ESP32C3
 * @version 0.4
 * @date 2021-9-15
 *
 * @copyright MIT License
 *
 */
#include "sdkconfig.h" // 包含ESP-IDF的配置头文件

#include <stdbool.h> // 引入布尔类型支持

#include "spi_switch.h"     // SPI切换功能声明
#include "cmsis_compiler.h" //
// 包含spi通用头文件
#include "soc/io_mux_reg.h"
#include "soc/spi_struct.h"
#include "soc/spi_reg.h"
#include "hal/gpio_ll.h"
#include "hal/aes_hal.h"
#include "soc/periph_defs.h"
#include "hal/clk_gate_ll.h"
#include "esp_log.h"

//GPSPI2支持io-mux直接输出
#define DAP_SPI GPSPI2 // ESP32S3使用GPSPI2控制器

// 临界区保护宏
#define ENTER_CRITICAL() portENTER_CRITICAL() // 进入临界区
#define EXIT_CRITICAL()  portEXIT_CRITICAL()  // 退出临界区

// SPI时钟分频定义
typedef enum {
    SPI_40MHz_DIV = 2, // 40MHz时钟分频值
    SPI_80MHz_DIV = 1, //// FIXME: 高速时钟(暂未使用)
} spi_clk_div_t;

void DAP_SPI_Init()
{
    ESP_LOGI("MdLink", "DAP_SPI_Init");
    // 启用SPI2时钟并解除复位
    periph_ll_enable_clk_clear_rst(PERIPH_SPI2_MODULE);

    // 停止GPIO驱动
    gpio_ll_set_level(&GPIO, GPIO_NUM_12, 0); // 设置GPIO12低电平
    gpio_ll_set_level(&GPIO, GPIO_NUM_11, 0); // 设置GPIO11低电平

    // 配置SCLK引脚(IO12)使用IO_MUX
    gpio_ll_iomux_in(&GPIO, GPIO_NUM_12, FSPICLK_IN_IDX);          // 设置输入功能
    gpio_ll_iomux_out(&GPIO, GPIO_NUM_12, FUNC_GPIO12_FSPICLK, 0); // 设置输出功能

    // 配置MOSI引脚(IO11)
    gpio_ll_iomux_in(&GPIO, GPIO_NUM_11, FSPID_IN_IDX);          // 设置输入功能
    gpio_ll_iomux_out(&GPIO, GPIO_NUM_11, FUNC_GPIO11_FSPID, 0); // 设置输出功能

    // 配置输出使能
    GPIO.func_out_sel_cfg[GPIO_NUM_11].oen_sel = 0; // 使用外设输出使能
    GPIO.func_out_sel_cfg[GPIO_NUM_12].oen_sel = 0; // 使用外设输出使能

    // 禁用DMA配置
    DAP_SPI.user.usr_conf_nxt = 0;   // 禁用用户配置
    DAP_SPI.slave.usr_conf = 0;      // 禁用从机配置
    DAP_SPI.dma_conf.dma_rx_ena = 0; // 禁用接收DMA
    DAP_SPI.dma_conf.dma_tx_ena = 0; // 禁用发送DMA

    // 设置为Master模式
    DAP_SPI.slave.slave_mode = false;

    // 使用全部64字节缓冲区
    /*write-data phase only access to high-part of the buffer spi_w8~spi_w15. 1: enable 0: disable.  Can be configured in CONF state.*/
    DAP_SPI.user.usr_mosi_highpart = false; // MOSI不使用高位
    DAP_SPI.user.usr_miso_highpart = false; // MISO不使用高位

    // 禁用所有CS引脚
    DAP_SPI.user.cs_setup = false; // 禁用CS建立时间
    DAP_SPI.user.cs_hold = false;  // 禁用CS保持时间
    DAP_SPI.misc.cs0_dis = 1;      // 禁用CS0
    DAP_SPI.misc.cs1_dis = 1;      // 禁用CS1
    DAP_SPI.misc.cs2_dis = 1;      // 禁用CS2
    DAP_SPI.misc.cs3_dis = 1;      // 禁用CS3
    DAP_SPI.misc.cs4_dis = 1;      // 禁用CS4
    DAP_SPI.misc.cs5_dis = 1;      // 禁用CS5
                                   /*Set the bit to enable full duplex communication. 1: enable 0: disable. Can be configured in CONF state.*/
    // 半双工传输模式
    DAP_SPI.user.doutdin = false;

    // 设置数据位顺序,swd就是低位在前
    DAP_SPI.ctrl.wr_bit_order = 1; // 写数据LSB优先
    DAP_SPI.ctrl.rd_bit_order = 1; // 读数据LSB优先

    // 设置dummy位
    DAP_SPI.user.usr_dummy = 0; // 不使用dummy周期

    /*clkcnt_n=1：总计数周期为2（值从0计数到1）。

clkcnt_h=0：高电平持续1个计数周期（计数器值≤0时输出高电平）。

clkcnt_l=1：低电平持续1个计数周期（计数器值>0且≤1时输出低电平）。*/
    // 配置SPI时钟 (40MHz, 50%占空比)
    DAP_SPI.clock.clk_equ_sysclk = false;           // 不使用系统时钟
    DAP_SPI.clock.clkdiv_pre = 0;                   // 预分频器为0
    DAP_SPI.clock.clkcnt_n = SPI_40MHz_DIV - 1;     // 总时钟周期数
    DAP_SPI.clock.clkcnt_h = SPI_40MHz_DIV / 2 - 1; // 高电平周期数
    DAP_SPI.clock.clkcnt_l = SPI_40MHz_DIV - 1;     // 低电平周期数

    // MISO延迟设置
    DAP_SPI.user.rsck_i_edge = true; // 在时钟边沿采样
    // 禁用所有数据线的延迟模式
    DAP_SPI.din_mode.din0_mode = 0; // DIN0无延迟
    DAP_SPI.din_mode.din1_mode = 0; // DIN1无延迟
    DAP_SPI.din_mode.din2_mode = 0; // DIN2无延迟
    DAP_SPI.din_mode.din3_mode = 0; // DIN3无延迟
    // 禁用所有数据线
    DAP_SPI.din_num.din0_num = 0; // 不使用DIN0
    DAP_SPI.din_num.din1_num = 0; // 不使用DIN1
    DAP_SPI.din_num.din2_num = 0; // 不使用DIN2
    DAP_SPI.din_num.din3_num = 0; // 不使用DIN3

    // 设置时钟极性和相位 (CPOL=1, CPHA=0)
    // 模式2
    DAP_SPI.misc.ck_idle_edge = 1; // 空闲时时钟为高电平
    DAP_SPI.user.ck_out_edge = 0;  // 数据在时钟下降沿输出

    // 启用SPI时钟门控
    DAP_SPI.clk_gate.clk_en = 1;         // 使能时钟
    DAP_SPI.clk_gate.mst_clk_active = 1; // 主时钟激活
    /*该位用于选择主机模式下SPI 模块的时钟源。1：选择PLL_F80M_CLK；0：选择XTAL_CLK。(R/W)*/
    DAP_SPI.clk_gate.mst_clk_sel = 1; // 选择主时钟

    // 禁用命令和地址阶段
    DAP_SPI.user.usr_command = 0; // 不使用命令阶段
    DAP_SPI.user.usr_addr = 0;    // 不使用地址阶段
}
#include "esp_log.h"
__FORCEINLINE void DAP_SPI_Deinit()
{
    ESP_LOGE("spi_switch", "DAP_SPI_Deinit");
    // 将引脚切换回GPIO功能
    gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[GPIO_NUM_12], PIN_FUNC_GPIO); // GPIO12设为普通GPIO
    gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[GPIO_NUM_11], PIN_FUNC_GPIO); // GPIO11设为普通GPIO

    // 配置SWCLK(IO12)为普通输出
    GPIO.func_out_sel_cfg[GPIO_NUM_12].func_sel = CORE1_GPIO_OUT1_IDX; // 选择核心1输出通道1
    GPIO.func_out_sel_cfg[GPIO_NUM_12].oen_sel = 1;                    // 使用软件控制输出使能
    gpio_ll_output_enable(&GPIO, GPIO_NUM_12);                         // 使能输出

    // 配置SWDIO(IO11)为普通输出
    GPIO.func_out_sel_cfg[GPIO_NUM_11].func_sel = CORE1_GPIO_OUT0_IDX; // 选择核心1输出通道0
    GPIO.func_out_sel_cfg[GPIO_NUM_11].oen_sel = 1;                    // 使用软件控制输出使能
    gpio_ll_output_enable(&GPIO, GPIO_NUM_11);                         // 使能输出

    // 配置SWDIO(IO11)为输入
    GPIO.func_in_sel_cfg[CORE1_GPIO_IN0_IDX].sig_in_sel = 1;         // 选择输入信号源
    GPIO.func_in_sel_cfg[CORE1_GPIO_IN0_IDX].func_sel = GPIO_NUM_11; // 映射到GPIO11
    gpio_ll_input_enable(&GPIO, GPIO_NUM_11);                        // 使能输入
}
// ESP32S3平台的SPI控制权获取
/**
 * @brief 获取SPI控制权
 */
__FORCEINLINE void DAP_SPI_Acquire()
{
    gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[GPIO_NUM_12], FUNC_GPIO12_FSPICLK); // 将GPIO12设为SPI CLK功能
}

/**
 * @brief 释放SPI控制权
 */
__FORCEINLINE void DAP_SPI_Release()
{
    gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[GPIO_NUM_12], FUNC_GPIO12_GPIO12); // 将GPIO12设为普通GPIO功能
}
